package com.gitee.melin.bee.core.jdbc.relational;

import java.util.NoSuchElementException;
import java.util.StringTokenizer;

public class DatabaseVersion {
    /**
     * Constant used to indicate that no version is defined
     */
    private static final int NO_VERSION = -9999;

    private static final char SEPARATOR = '.';

    private static final String SEPARATOR_STRING = ".";

    private final int major;
    private final int minor;
    private final int micro;
    private transient String versionString;

    public DatabaseVersion(int major, int minor, int micro) {
        this.major = major;
        this.minor = minor;
        this.micro = micro;
        validate();
    }

    public DatabaseVersion(String version) {
        int maj = 0;
        int min = 0;
        int mic = 0;

        try {
            StringTokenizer st = new StringTokenizer(version, SEPARATOR_STRING,
                    true);
            maj = parseInt(st.nextToken(), version);

            if (st.hasMoreTokens()) { // minor
                st.nextToken(); // consume delimiter
                min = parseInt(st.nextToken(), version);

                if (st.hasMoreTokens()) { // micro
                    st.nextToken(); // consume delimiter
                    mic = parseInt(st.nextToken(), version);

                    if (st.hasMoreTokens()) { // qualifier separator
                        st.nextToken(); // consume delimiter

                        if (st.hasMoreTokens()) { // fail safe
                            throw new IllegalArgumentException("invalid version \"" + version + "\": invalid format");
                        }
                    }
                }
            }
        } catch (NoSuchElementException e) {
            throw new IllegalArgumentException(
                    "invalid version \"" + version + "\": invalid format", e);
        }

        major = maj;
        minor = min;
        micro = mic;
        validate();
    }

    private int parseInt(String value, String version) {
        try {
            return Integer.parseInt(value);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("invalid version \"" + version
                    + "\": non-numeric \"" + value + "\"", e);
        }
    }

    private void validate() {
        if (major < 0) {
            throw new IllegalArgumentException("invalid version \"" + toString0() + "\": negative number \"" + major + "\"");
        }
        if (minor < 0) {
            throw new IllegalArgumentException("invalid version \"" + toString0() + "\": negative number \"" + minor + "\"");
        }
        if (micro < 0) {
            throw new IllegalArgumentException("invalid version \"" + toString0() + "\": negative number \"" + micro + "\"");
        }
    }

    private String toString0() {
        String s = versionString;
        if (s != null) {
            return s;
        }

        String result = String.valueOf(major) +
                SEPARATOR +
                minor +
                SEPARATOR +
                micro;
        return versionString = result;
    }

    public int getDatabaseMajorVersion() {
        return major;
    }

    public int getDatabaseMinorVersion() {
        return minor;
    }

    public int getDatabaseMicroVersion() {
        return micro;
    }

    public int getMajor() {
        return major;
    }

    public int getMinor() {
        return minor;
    }

    public int getMicro() {
        return micro;
    }

    /**
     * Determine if the versions are the same/equal.
     */
    public boolean isSame(DatabaseVersion other) {
        return isSame( other.getDatabaseMajorVersion(), other.getDatabaseMinorVersion(), other.getDatabaseMicroVersion() );
    }

    /**
     * Determine if this version matches the passed one.
     */
    public boolean isSame(int otherMajor) {
        return getDatabaseMajorVersion() == otherMajor;
    }

    /**
     * Determine if this version matches the passed one.
     */
    public boolean isSame(int otherMajor, int otherMinor) {
        return isSame( otherMajor ) && getDatabaseMinorVersion() == otherMinor;
    }

    /**
     * Determine if this version matches the passed one.
     */
    public boolean isSame(int otherMajor, int otherMinor, int otherMicro) {
        return isSame( otherMajor, otherMinor ) && getDatabaseMicroVersion() == otherMicro;
    }

    /**
     * {@link #isSame} or {@link #isAfter}
     */
    public boolean isSameOrAfter(DatabaseVersion other) {
        return isSameOrAfter( other.getDatabaseMajorVersion(), other.getDatabaseMinorVersion() );
    }

    /**
     * {@link #isSame} or {@link #isAfter}
     */
    public boolean isSameOrAfter(Integer otherMajor, Integer otherMinor) {
        return isSameOrAfter(
                (int) otherMajor,
                otherMinor == null ? NO_VERSION : otherMinor
        );
    }

    /**
     * {@link #isSame} or {@link #isAfter}
     */
    public boolean isSameOrAfter(int otherMajor) {
        final int major = getDatabaseMajorVersion();

        return major >= otherMajor;
    }

    /**
     * {@link #isSame} or {@link #isAfter}
     */
    public boolean isSameOrAfter(int otherMajor, int otherMinor) {
        final int major = getDatabaseMajorVersion();
        final int minor = getDatabaseMinorVersion();

        return major > otherMajor
                || ( major == otherMajor && minor >= otherMinor );
    }

    /**
     * {@link #isSame} or {@link #isAfter}
     */
    public boolean isSameOrAfter(int otherMajor, int otherMinor, int otherMicro) {
        final int major = getDatabaseMajorVersion();
        final int minor = getDatabaseMinorVersion();
        final int micro = getDatabaseMicroVersion();

        return major > otherMajor
                || ( major == otherMajor && minor > otherMinor )
                || ( major == otherMajor && minor == otherMinor && micro >= otherMicro );
    }

    /**
     * Determine whether this version comes after the passed one
     */
    public boolean isAfter(DatabaseVersion other) {
        return isAfter( other.getDatabaseMajorVersion(), other.getDatabaseMinorVersion() );
    }

    /**
     * Determine whether this version after the passed one
     */
    public boolean isAfter(Integer major, Integer minor) {
        return isAfter( (int) major, minor == null ? NO_VERSION : minor );
    }

    /**
     * Determine whether this version after the passed one
     */
    public boolean isAfter(int major) {
        return getDatabaseMajorVersion() > major;
    }

    /**
     * Determine whether this version after the passed one
     */
    public boolean isAfter(int major, int minor) {
        return getDatabaseMajorVersion() > major
                || ( getDatabaseMajorVersion() == major && getDatabaseMinorVersion() > minor );
    }

    /**
     * Determine whether this version after the passed one
     */
    public boolean isAfter(int otherMajor, int otherMinor, int otherMicro) {
        final int major = getDatabaseMajorVersion();
        final int minor = getDatabaseMinorVersion();
        final int micro = getDatabaseMicroVersion();

        return major > otherMajor
                || ( major == otherMajor && minor > otherMinor )
                || ( major == otherMajor && minor == otherMinor && otherMicro > micro );
    }

    /**
     * Determine whether this version comes before the passed one
     */
    public boolean isBefore(DatabaseVersion other) {
        return isBefore( other.getDatabaseMajorVersion(), other.getDatabaseMinorVersion() );
    }

    /**
     * Determine whether this version before the passed one
     */
    public boolean isBefore(int major, int minor) {
        return ! isSameOrAfter( major, minor );
    }

    /**
     * Determine whether this version before the passed one
     */
    public boolean isBefore(int major) {
        return ! isSameOrAfter( major );
    }

    /**
     * Determine whether this version before the passed one
     */
    public boolean isBefore(Integer major, Integer minor) {
        return isBefore( (int) major, minor == null ? NO_VERSION : minor );
    }

    /**
     * Determine whether this version before the passed one
     */
    public boolean isBefore(int otherMajor, int otherMinor, int otherMicro) {
        return ! isSameOrAfter( otherMajor, otherMinor, otherMicro );
    }
}
